GSoC23 — Workweek 5

Introduction

I am excited to tell you that I am in the midst of completing my first feature addition for Icarus Verilog.

As you may know if you've read my previous posts, most of my work so far has been reading and reorganizing the documentation, understanding what needs to be implemented and how, opening issues, and creating small PRs to clean things up.

But no real step towards the goal of this project. That has changed, because I opened my first PR #966 with non-trivial changes to Icarus Verilog.

What is it about?

Well, I think I need to explain this from the beginning.

You may be aware of the setup/hold time of a flip-flop. Together these times form a window in which the input signal of a flip-flop must be stable. The setup time is the amount of time required for the input to be stable before a clock edge. Similarly the hold time is the amount of time required for the input to be stable after a clock edge.

This is best grasped visually:

Setup/Hold window

However, flip-flops can also have a negative setup or hold time. How is this possible? Because of additional circuitry around the flip-flop, either the data or clock is delayed resulting in negative setup or hold time. Think of a scan-chain flip-flop: Here, a multiplexer is connected to the data input, which increases the delay. If big enough, the hold time can become negative.

Setup/Hold window with negative hold time

The simulator must also be able to handle negative timing checks. For this to work, the data or clock signals must be delayed.

For example the $setuphold timing check can be annotated with a negative setup or hold time. The simulator then applies either a delay from sig1 to del_sig1 or from sig2 to del_sig2 in order to be able to perform the timing check:

$setuphold(posedge sig1, negedge sig2 , 0:0:0 , 0:0:0 , notifier , cond1 , cond2 , del_sig1 , del_sig2 ) ;

Now, while I haven't implemented the timing checking myself - that's a whole other topic - there is still something we can do if timing checks are disabled (or in the case of Icarus Verilog, not yet supported): The delayed reference and data signals become copies of the original reference and data signals.

For the above code, this means that the simulator makes such an assignment internally:

assign del_sig1 = sig1;
assign del_sig2 = sig2;

Why is it important that these signals are assigned? The Verilog model might use these signals and without them the simulation is not possible. This approach is not absolutely correct, but it is better than nothing and correct in the sense of section "Option behavior" in the standard.

Implementation Approach

If you have read my previous posts, you already now that the Icarus Verilog Compilation System consists of several modules.

ivl parses the design and generates an internal netlist which is then passed to the code generators. This is where we want to step in.

Our goal is to:

  • Parse the timing checks
  • Store the necessary information as "pform" (parsed form)
  • Upon elaboration perform the necessary assignments

Parse the timing checks

Icarus uses the tools "flex", "gperf", and "bison" for lexical analysis. For this work, Bison is interesting to us.

The parse.y file contains all the rules for parsing (System)Verilog Syntax. Luckily for us, there were already some rules to parse timing checks, but they weren't doing anything besides printing "not supported". So I extended both rules for $setuphold and $recrem to receive all optional arguments and pass them on as pform. Before we continue, I want to address a typical problem with bottom-up parsers such as bison: shift/reduce conflicts.

Shift/Reduce Conflicts

A shift/reduce conflict is a situation where both a shift or reduction would be valid. You can read all about these conflicts on the bison manual.

Nevertheless, parsing of the timing checks still worked for me because Bison is designed to resolve these conflicts by choosing to shift. But eventually I should find a way to properly handle this.

Store as pform

Next, we store the parsed information as pform. The pform is what compiler writers call a “decorated parse tree”.

To store the parsed information, I created the base class PTimingCheck and the two classes PSetupHold and PRecRem that inherit from the base class.

These classes have members for all the parameters parsed in the previous step and also implement the function void elaborate(class Design*des, class NetScope*scope) const.

In pform.cc I added tow functions pform_make_setup_hold and pform_make_rec_rem which return their respective objects. The function pform_module_timing_check then adds these timing checks to the current active module.

Elaboration

During elaboration all pform objects are transformed to the netlist form. This is exactly what we want to do for our assignments. Therefore, I add a hook to the Module's elaboration function to also call the elaboration functions of all timing checks.

// Elaborate the timing checks of the module.
for (list<PTimingCheck*>::const_iterator tc = timing_checks.begin()
    ; tc != timing_checks.end() ; ++ tc ) {

    (*tc)->elaborate(des, scope);
}

Finally, in the elaboration for $setuphold and $recrem we check whether delayed_reference_ or delayed_data_ is available. If yes, then these signals are connected to the respective reference / data signals.

Summary

This is my first feature implementation in Icarus Verilog, with more to come.

The PR is currently open in #966. There will be more changes before it is merged, hopefully with no blocking issues.

See you next week!